﻿using System;
using System.Collections.Generic;
using System.IO;
using Bunbyx.exception;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;

namespace Bunbyx.game
{
    public class Level
    {
        // --------------------------------------------------------------------
        //  Constants
        // --------------------------------------------------------------------

        private const string LEVELS_FOLDER = "Levels";
        private const string TERRAIN_RESOURCE_NAME = "Terrain";
        private const string TRACK_RESOURCE_NAME = "Track";
        private const string OVERLAY_RESOURCE_NAME = "Overlay";
        private const string LEVEL_DESCRIPTION_NAME = "Level";

        private const string SCALE = "scale";
        private const string OVERLAY = "overlay";
        private const string BACKGROUND_PARTS = "bgparts";
        private const string START = "start";
        private const string GOAL = "goal";
        private const string TIME = "time";
        private const string CARROT = "carrot";
        private const string SALAD = "salad";
        private const string IDOL_GOLD = "idol";

        private char[] SEPARATORS = { '=', ',' };

        // --------------------------------------------------------------------
        //  Delegates
        // --------------------------------------------------------------------

        public delegate void ItemPickedUp(int value, Sprite.SpriteType type);
        public delegate void LevelEnded();

        // --------------------------------------------------------------------
        //  Public attributes
        // --------------------------------------------------------------------

        // Counters
        public int SaladCount
        {
            get;
            private set;
        }

        public int CarrotCount
        {
            get;
            private set;
        }

        public int IdolCount
        {
            get;
            private set;
        }

        public int MaxTime
        {
            get;
            private set;
        }

        public string Name
        {
            get;
            private set;
        }

        // --------------------------------------------------------------------
        //  Private attributes
        // --------------------------------------------------------------------

        // Camera
        private Camera camera;

        // Player controls
        private KeyboardListener keyboard;

        // Collision detector
        private CollisionDetector collisionDetector;

        // Sound player
        private SoundPlayer soundPlayer;

        // Content manager
        private ContentManager content;

        // Sprites
        private BunbyX bunbyx;
        private Dictionary<int, Sprite> background;
        private Dictionary<int, Sprite> backgroundTrack;
        private Dictionary<int, Sprite> overlay;
        private Sprite goal;
        private List<Sprite> sprites;
        private List<Sprite> spritesToDelete;

        // Misc
        private bool hasOverlay;
        private float bgScale;
        private int bgRows;
        private int bgCols;
        private Rectangle container;

        // Handlers
        private ItemPickedUp itemPickUpHandler;
        private LevelEnded endOfLevelHandler;

        // --------------------------------------------------------------------
        //  Constructor
        // --------------------------------------------------------------------

        public Level(string levelName, Camera camera, KeyboardListener keyboard, SoundPlayer soundPlayer, ItemPickedUp itemPickUpHandler, LevelEnded endOfLevelHandler)
        {
            this.Name = levelName;
            this.camera = camera;
            this.keyboard = keyboard;
            this.soundPlayer = soundPlayer;
            this.itemPickUpHandler = itemPickUpHandler;
            this.endOfLevelHandler = endOfLevelHandler;

            InitializeLevel();
        }

        // --------------------------------------------------------------------
        //  Public methods
        // --------------------------------------------------------------------

        public void InitializeLevel()
        {
            MaxTime = -1;
            SaladCount = 0;
            CarrotCount = 0;
            IdolCount = 0;

            // Initialize sprites
            background = new Dictionary<int, Sprite>();
            backgroundTrack = new Dictionary<int, Sprite>();
            overlay = new Dictionary<int, Sprite>();
            sprites = new List<Sprite>();
            spritesToDelete = new List<Sprite>();
            List<Sprite> items = new List<Sprite>();

            // Background default values
            hasOverlay = false;
            bgScale = 1;
            bgRows = 1;
            bgCols = 1;

            // Reset to default
            bunbyx = null;
            goal = null;

            // Read level description
            string[] levelDescription = File.ReadAllLines(BunbyxGame.CONTENT_ROOT_DIRECTORY + "/" + LEVELS_FOLDER + "/" + Name + "/" + LEVEL_DESCRIPTION_NAME);
            int ctLine = 1;

            foreach (string levelLine in levelDescription)
            {
                if (levelLine.Trim().Length > 0)
                {
                    string[] parameters = levelLine.Split(SEPARATORS);

                    switch (parameters[0].Trim())
                    {
                        // Read scale
                        case SCALE:
                            try
                            {
                                bgScale = float.Parse(parameters[1].Trim());
                            }
                            catch (FormatException)
                            {
                                throw new LevelInitializationException("Error at line " + ctLine + ": Scale must be a float (" + parameters[1] + ")");
                            }
                            break;

                        // Read number of background parts
                        case BACKGROUND_PARTS:
                            try
                            {
                                bgRows = int.Parse(parameters[1].Trim());
                                bgCols = int.Parse(parameters[2].Trim());
                            }
                            catch (FormatException)
                            {
                                throw new LevelInitializationException("Error at line " + ctLine + ": Background parts must be two integers representing the number of rows and cols of the megatexture");
                            }
                            break;

                        // Read whether there is an overlay
                        case OVERLAY:
                            try
                            {
                                hasOverlay = bool.Parse(parameters[1]);
                            }
                            catch (FormatException)
                            {
                                throw new LevelInitializationException("Error at line " + ctLine + ": Overlay must be either true or false (" + parameters[1] + ")");
                            }
                            break;

                        // Read start position
                        case START:
                            if (bunbyx != null)
                            {
                                throw new LevelInitializationException("Error at line " + ctLine + ": Start position can only be declared once");
                            }
                            else
                            {
                                // Create Bunbyx
                                bunbyx = new BunbyX(fetchPosition(parameters, ctLine), soundPlayer);
                            }
                            break;

                        // Read goal position
                        case GOAL:
                            if (goal != null)
                            {
                                throw new LevelInitializationException("Error at line " + ctLine + ": Goal position can only be declared once");
                            }
                            else
                            {
                                // Create goal
                                goal = new Sprite(Sprite.SpriteType.Goal, fetchPosition(parameters, ctLine));
                                items.Add(goal);
                            }
                            break;

                        // Read max time
                        case TIME:
                            if (MaxTime != -1)
                            {
                                throw new LevelInitializationException("Error at line " + ctLine + ": Time can only be declared once");
                            }
                            else
                            {
                                try
                                {
                                    MaxTime = int.Parse(parameters[1].Trim());

                                    if (MaxTime <= 0)
                                    {
                                        throw new LevelInitializationException("Error at line " + ctLine + ": Time must be a positive non null integer (" + parameters[1] + ")");
                                    }
                                }
                                catch (FormatException)
                                {
                                    throw new LevelInitializationException("Error at line " + ctLine + ": Time must be an integer (" + parameters[1] + ")");
                                }
                            }
                            break;

                        // Read carrot
                        case CARROT:
                            Sprite carrot = new Sprite(Sprite.SpriteType.Carrot, fetchPosition(parameters, ctLine));
                            items.Add(carrot);
                            CarrotCount++;
                            break;

                        // Read salad
                        case SALAD:
                            Sprite salad = new Sprite(Sprite.SpriteType.Salad, fetchPosition(parameters, ctLine));
                            items.Add(salad);
                            SaladCount++;
                            break;

                        case IDOL_GOLD:
                            Sprite idol = new Sprite(Sprite.SpriteType.IdolGold, fetchPosition(parameters, ctLine));
                            items.Add(idol);
                            IdolCount++;
                            break;
                        // Unknown item
                        default:
                            throw new LevelInitializationException("Error at line " + ctLine + ": Unknown item (" + parameters[0].Trim() + ")");
                    }
                }

                ctLine++;
            }

            // Missing start position
            if (bunbyx == null)
            {
                throw new LevelInitializationException("Missing start position");
            }

            // Missing goal position
            if (goal == null)
            {
                throw new LevelInitializationException("Missing goal position");
            }

            // Load background, track and overlay
            for (int i = 0; i < bgRows; i++)
            {
                for (int j = 0; j < bgCols; j++)
                {
                    int index = i * bgRows + j;

                    // Create background part
                    Sprite backgroundPart = new Sprite(LEVELS_FOLDER + "/" + Name + "/" + TERRAIN_RESOURCE_NAME + "_" + i + "-" + j);
                    backgroundPart.Scale = bgScale;
                    background.Add(index, backgroundPart);

                    // Create background part collision mask
                    Sprite backgroundTrackPart = new Sprite(LEVELS_FOLDER + "/" + Name + "/" + TRACK_RESOURCE_NAME + "_" + i + "-" + j);
                    backgroundTrackPart.Scale = bgScale;
                    backgroundTrackPart.IsDrawable = false;
                    backgroundTrack.Add(index, backgroundTrackPart);

                    // Create overlay
                    if (hasOverlay)
                    {
                        Sprite overlayPart = new Sprite(LEVELS_FOLDER + "/" + Name + "/" + OVERLAY_RESOURCE_NAME + "_" + i + "-" + j);
                        overlayPart.Scale = bgScale;
                        overlay.Add(index, overlayPart);
                    }
                }
            }

            // Insert sprites in drawing order
            sprites.AddRange(background.Values);
            sprites.AddRange(backgroundTrack.Values);
            sprites.AddRange(items);
            sprites.Add(bunbyx);
            sprites.AddRange(overlay.Values);

            // Create player controls
            keyboard.BunbyX = bunbyx;

            // Create collision detector
            collisionDetector = new CollisionDetector(backgroundTrack, bunbyx, sprites, CollisionDetected);
        }

        public void LoadContent(ContentManager content)
        {
            this.content = content;

            // Load sprite contents
            foreach (Sprite sprite in sprites)
            {
                sprite.LoadContent(content);
            }

            // Create container
            container = new Rectangle(0, 0, 0, 0);

            // Replace background
            float bgy = 0;
            for (int i = 0; i < bgRows; i++)
            {
                float bgx = 0;
                float height = 0;
                for (int j = 0; j < bgCols; j++)
                {
                    int index = i * bgRows + j;
                    Vector2 position = new Vector2(bgx, bgy);

                    Sprite backgroundPart = background[index];
                    backgroundPart.Position = position;
                    backgroundTrack[index].Position = position;

                    if (hasOverlay)
                    {
                        overlay[index].Position = position;
                    }

                    // Update X
                    bgx += backgroundPart.Size.Width;
                    height = backgroundPart.Size.Height;

                    if (i == 0)
                    {
                        container.Width += (int)(backgroundPart.Size.Width);
                    }
                }

                // Update Y
                bgy += height;
                container.Height += (int)(height);
            }

            // Constrain camera
            camera.Container = container;

            // Start music
            soundPlayer.PlayMusic(Music.Background);
        }

        public void Update(GraphicsDeviceManager graphics, SpriteBatch spriteBatch, GameTime gameTime)
        {
            // Delete sprites
            foreach (Sprite sprite in spritesToDelete)
            {
                sprites.Remove(sprite);
            }
            spritesToDelete.Clear();

            // Init Bunbyx
            bunbyx.PreUpdate();

            // Update player controls
            keyboard.Update(false);

            // Update sprites
            foreach (Sprite sprite in sprites)
            {
                sprite.Update(gameTime);
            }

            // Dumbledore dies
            if (bunbyx.Position.Y > container.Height)
            {
                // NAO RESTART
                RestartLevel();
            }
            else
            {
                // Make the camera follow Bunbyx
                camera.Follow(bunbyx);

                // Detect collisions
                collisionDetector.DetectCollisions(graphics, spriteBatch);
            }
        }

        public void CollisionDetected(Sprite collider)
        {
            switch (collider.Type)
            {
                case Sprite.SpriteType.Goal:
                    soundPlayer.StopMusic();
                    endOfLevelHandler();
                    break;

                case Sprite.SpriteType.Carrot:
                    bunbyx.SpeedUp();
                    spritesToDelete.Add(collider);
                    itemPickUpHandler(ScoreScreen.CARROT_MULTIPLIER, Sprite.SpriteType.Carrot);
                    break;

                case Sprite.SpriteType.Salad:
                    bunbyx.JumpUp();
                    spritesToDelete.Add(collider);
                    itemPickUpHandler(ScoreScreen.SALAD_MULTIPLIER, Sprite.SpriteType.Salad);
                    break;

                case Sprite.SpriteType.IdolGold:
                    bunbyx.SuperBunbyxWJ();
                    spritesToDelete.Add(collider);
                    itemPickUpHandler(ScoreScreen.GOLD_IDOL_MULTIPLIER, Sprite.SpriteType.IdolGold);
                    break;
            }
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            foreach (Sprite sprite in sprites)
            {
                sprite.Draw(spriteBatch);
            }
        }

        public void RestartLevel()
        {
            InitializeLevel();
            LoadContent(content);
        }

        // --------------------------------------------------------------------
        //  Private methods
        // --------------------------------------------------------------------

        private Vector2 fetchPosition(string[] parameters, int ctLine)
        {
            try
            {
                return new Vector2(int.Parse(parameters[1].Trim()), int.Parse(parameters[2].Trim()));
            }
            catch (FormatException)
            {
                throw new LevelInitializationException("Error at line " + ctLine + ": X and Y values are not integers (" + parameters[1] + ", " + parameters[2] + ")");
            }
            catch (IndexOutOfRangeException)
            {
                throw new LevelInitializationException("Error at line " + ctLine + ": Line is not valid, muse have 3 values (type=x,y)");
            }
        }
    }
}
